package bayes;

import java.awt.Point;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import bayes.nodeTypes.Climate;
import bayes.nodeTypes.IceCondition;
import bayes.nodeTypes.IceSense;
import bayes.nodeTypes.Season;
import bayes.nodeTypes.Variable;
import domain.Board_Ass4;
import domain.Debug;
import domain.SquareIce;


public class BayesNetwork {

	private class SiteNode extends Node{
		public int		_index;
		public Point	_location;
		public SiteNode(Variable variable, int _index, Point _location, String name, Node parent, int domainSize) {
			super(variable, name, parent, domainSize);
			this._index 	= _index;
			this._location	= _location;
		}
	}


	public static final int 	NO_PARENT_DOAMIN_VALUE 	= 0;
	public static final double 	P_NEAR 					= 0.9;		// TODO:get from program input
	public static final double 	P_FAR					= 0.7;		// TODO:get from program input
	private static final double NEAR_DISTANCE 			= 5.0;
	private int					SITE_INDEX 				= 0;
	private int 				SENSE_INDEX				= 0;

	private Board_Ass4				_board;
	private String					_configurationFile;

	// Network nodes:
	private	Node					_root;
	private HashMap<Integer, Node>	_regions;			//TODO delete it?
	private Vector<Node> 			_regionss;			//TODO or delete it?
	private Vector<Node>		_sites;

	private Vector<Node>			_measurements;

	public Vector<Node> get_measurements() {
		return _measurements;
	}

	// data from configuration file:
	private double 					_winterProbability;
	private double 					_summerProbability;
	private Vector<double[][]>		_regionsProbabilities;
	private double 					_iceInWarmProbability;
	private double 					_iceInColdProbability;
	private double 					_iceInFrigidProbability;



	public BayesNetwork(Board_Ass4 board, String configurationFile) {
		this._board					= board;
		this._configurationFile		= configurationFile;
		this._regions 				= new HashMap<Integer, Node>();
		this._regionss 				= new Vector<Node>();
		this._sites 				= new Vector<Node>();
		this._measurements 			= new Vector<Node>();
		this._regionsProbabilities 	= new Vector<double[][]>();
		readConfigurationFile();
		construct();
	}

	public Node get_root() {
		return _root;
	}

	// starting at 1:
	public Node get_regions(int regionIndex) {
		return _regions.get(regionIndex);
	}

	// starting at 0:
	public Node get_site(int siteIndex) {
		return _sites.elementAt(siteIndex);
	}

	public int getNuberOfsites() {
		return _sites.size();
	}


	// starting at 0:
	public Node get_measurements(int measureIndex) {
		return _measurements.elementAt(measureIndex);
	}

	// this method parsing the configuration file and initializes all probabilities according to it:
	private void readConfigurationFile(){
        try {
            BufferedReader in = new BufferedReader(new FileReader(this._configurationFile));
            String str;
            while ((str = in.readLine()) != null) {
            	if (str.equalsIgnoreCase("PROBABILITY OF SUMMER SEASON?")){
        			this._summerProbability = Double.parseDouble(in.readLine());
        			this._winterProbability = 1.0 - this._summerProbability;
            	}
            	else if (str.startsWith("DISTRIBUTION FOR REGION")){
            		if (str.contains("summer")){
            			double[][] region = new double[2][3];
	            		region[Season.SUMMER][Climate.WARM]		= Double.parseDouble(in.readLine());
	            		region[Season.SUMMER][Climate.COLD]		= Double.parseDouble(in.readLine());
	            		region[Season.SUMMER][Climate.FRIGID]	= Double.parseDouble(in.readLine());
	            		this._regionsProbabilities.addElement(region);
            		} else if (str.contains("winter")){
            			double[][] region = this._regionsProbabilities.lastElement();
	            		region[Season.WINTER][Climate.WARM]		= Double.parseDouble(in.readLine());
	            		region[Season.WINTER][Climate.COLD]		= Double.parseDouble(in.readLine());
	            		region[Season.WINTER][Climate.FRIGID]	= Double.parseDouble(in.readLine());
            		}
            	}
            	else if (str.startsWith("PROBABILITY OF ICE")){
            		if (str.contains("Warm")){
            			this._iceInWarmProbability = Double.parseDouble(in.readLine());
            		} else if (str.contains("Cold")){
            			this._iceInColdProbability = Double.parseDouble(in.readLine());
            		} else if (str.contains("Frigid")){
            			this._iceInFrigidProbability = Double.parseDouble(in.readLine());
            		}
            	}
            }
            in.close();
        } catch (IOException e) {
            Debug.println(e.getMessage());
        }
	}


	private void construct(){
		addNode_Season(_winterProbability, _summerProbability);

		// regions:
		for (Iterator<SquareIce> iterator = this._board.get_ices().iterator(); iterator.hasNext();) {
			SquareIce square = iterator.next();
			int regionIndex = square.get_region();
			if (! _regions.containsKey(regionIndex)){
				double[][] region_arr = _regionsProbabilities.elementAt(regionIndex-1);
				addNode_Region(region_arr[Season.WINTER][Climate.WARM],
							   region_arr[Season.WINTER][Climate.COLD],
							   region_arr[Season.WINTER][Climate.FRIGID],
							   region_arr[Season.SUMMER][Climate.WARM],
							   region_arr[Season.SUMMER][Climate.COLD],
							   region_arr[Season.SUMMER][Climate.FRIGID],
							   regionIndex);
			}
		}
		if (_regions.keySet().size() != _regionsProbabilities.size()){
			System.out.println(_regions.keySet().size());
			System.out.println(_regionsProbabilities.size());
			System.err.println("Regions in configuration file don't match regions in board");
			System.exit(-1);
		}

		// sites:
		for (Iterator<SquareIce> iterator = this._board.get_ices().iterator(); iterator.hasNext();) {
			SquareIce square = iterator.next();

			// constructing SiteData Object:
			addNode_Site(_iceInWarmProbability, 	1 - _iceInWarmProbability,
						 _iceInColdProbability, 	1 - _iceInColdProbability,
						 _iceInFrigidProbability, 	1 - _iceInFrigidProbability,
						 square);
		}
	}

	public void readSensor(int row, int column, boolean measurement){

		Node parentSite	= getParent(row, column);
		if (parentSite == null) {
			System.out.println("Sensing at square (" + row + "," + column + ") isn't possible because it's not icy.");
			System.out.println("Exiting program...");
			System.exit(-1);
		}

		// finding distance:
		double distance = 0.0;
		Point agentLocation = this._board.getAgentLocation();
		Point siteLocation  = new Point(row, column);
		distance = euclideanDistance(agentLocation, siteLocation);
		double a = computeProbability(distance);
		double b = 1 - a;

		addNode_Measure(a, b, b, a, parentSite, measurement);
	}

	private double computeProbability(double distance) {
		if (isNear(distance))
			return P_NEAR;
		else
			return P_FAR;
	}

	private boolean isNear(double distance) {
		return distance <= NEAR_DISTANCE;
	}

	private Node getParent(int row, int column) {
		Point givenLocation = new Point(row, column);

		for (Iterator<Node> iterator = _sites.iterator(); iterator.hasNext();) {
			Node siteNode = iterator.next();

			if (((SiteNode)siteNode)._location.equals(givenLocation)) return siteNode;
		}
		return null;
	}

	private double euclideanDistance(Point agentLocation, Point siteLocation) {
		double x1 = agentLocation.x;
		double y1 = agentLocation.y;
		double x2 = siteLocation.x;
		double y2 = siteLocation.y;
		return Math.sqrt((x1-x2)*(x1-x2)  +  (y1-y2)*(y1-y2));
	}

	public Vector<Node> getAllVariables() {
		Vector<Node> allVars = new Vector<Node>();

		allVars.addElement(this._root); /* also the season node*/
		allVars.addAll(this._regions.values());
		allVars.addAll(this._sites);
		allVars.addAll(this._measurements);

		return allVars;
	}

	private void addNode_Season(double probability, double probability2) {
		Season season = new Season();
		_root = new Node(season, "Season", Season.DOMAIN_LENGTH);
		_root.setProbability(0, Season.WINTER , _winterProbability);
		_root.setProbability(0, Season.SUMMER , _summerProbability);
	}

	private void addNode_Region(double winter_warm, double winter_cold, double winter_frigid,
							   double summer_warm, double summer_cold, double summer_frigid,
							   int regionIndex){
		Climate climate = new Climate();
		Node region = new Node(climate, "Region"+regionIndex, _root, Climate.DOMAIN_LENGTH);
		region.setProbability(Season.WINTER, Climate.WARM, 	winter_warm);
		region.setProbability(Season.WINTER, Climate.COLD, 	winter_cold);
		region.setProbability(Season.WINTER, Climate.FRIGID,winter_frigid);
		region.setProbability(Season.SUMMER, Climate.WARM, 	summer_warm);
		region.setProbability(Season.SUMMER, Climate.COLD, 	summer_cold);
		region.setProbability(Season.SUMMER, Climate.FRIGID,summer_frigid);

		this._root.addChild(region);
		this._regions.put(regionIndex, region);
		this._regionss.addElement(region);

	}

	private void addNode_Site(double warm_icy, double warm_not_icy,
							 double cold_icy, double cold_not_icy,
							 double frigid_icy, double frigid_not_icy,
			   				 SquareIce square){
		Node parent = _regions.get(square.get_region());
		IceCondition iceCondition = new IceCondition();
		int index	= SITE_INDEX++;
		String name	= "site_"+ index;
		Point point = new Point(square.getRow(), square.getColumn());
		SiteNode site = new SiteNode(iceCondition, index, point, name, parent , IceCondition.DOMAIN_LENGTH);

		// insert probabilities:
		site.setProbability(Climate.WARM, IceCondition.ICY,		warm_icy);
		site.setProbability(Climate.WARM, IceCondition.NOT_ICY,	warm_not_icy);
		site.setProbability(Climate.COLD, IceCondition.ICY,		cold_icy);
		site.setProbability(Climate.COLD, IceCondition.NOT_ICY,	cold_not_icy);
		site.setProbability(Climate.FRIGID, IceCondition.ICY,	frigid_icy);
		site.setProbability(Climate.FRIGID,IceCondition.NOT_ICY,frigid_not_icy);

		parent.addChild(site);
		this._sites.addElement(site);
	}

	private void addNode_Measure(double icy_and_says_icy, double icy_and_says_notIcy,
					 			double notIcy_and_says_icy, double notIcy_and_says_notIcy,
					 			Node parent, boolean measurement){
		if (parent.getNumberOfChildren() == 2) {
			System.out.println("Cannot perform more than 2 senses in an icy square.");
			System.out.println("Exiting program...");
			System.exit(-1);
		}
		IceSense iceSense 	= new IceSense();
		iceSense.setSensorMeasure(measurement);
		int index			= SENSE_INDEX++;
		String name			= "measure_"+ parent.get_index() + index;
		Node measure = new Node(iceSense, name, parent, IceSense.DOMAIN_LENGTH);

		// insert probabilities:
		measure.setProbability(IceCondition.ICY, IceSense.SENSE_ICY,			icy_and_says_icy);
		measure.setProbability(IceCondition.ICY, IceSense.SENSE_NOT_ICY,		icy_and_says_notIcy);
		measure.setProbability(IceCondition.NOT_ICY, IceSense.SENSE_ICY,		notIcy_and_says_icy);
		measure.setProbability(IceCondition.NOT_ICY, IceSense.SENSE_NOT_ICY,	notIcy_and_says_notIcy);

		parent.addChild(measure);
		this._measurements.addElement(measure);
	}

	public void print() {
		// Season:
		System.out.println("P(Winter) = " + this._winterProbability);
		System.out.println("P(Summer) = " + this._summerProbability);
		System.out.println();

		// Regions:
		int regionNumber = 1;
		for (Iterator<double[][]> iterator = this._regionsProbabilities.iterator(); iterator.hasNext();) {
			double[][] region = iterator.next();
			System.out.print("P(Region"+regionNumber+"|Winter) = (");
			System.out.print(region[Season.WINTER][Climate.WARM] + ", ");
			System.out.print(region[Season.WINTER][Climate.COLD] + ", ");
			System.out.println(region[Season.WINTER][Climate.FRIGID] + ")  ; (W, C, F)");
			System.out.print("P(Region"+regionNumber+"|Summer) = (");
			System.out.print(region[Season.SUMMER][Climate.WARM] + ", ");
			System.out.print(region[Season.SUMMER][Climate.COLD] + ", ");
			System.out.println(region[Season.SUMMER][Climate.FRIGID] + ")  ; (W, C, F)");
			System.out.println();
			regionNumber++;
		}

		// Sites:
		int siteNumber = 0;
		for (Iterator<Node> iterator = this._sites.iterator(); iterator.hasNext();) {
			Node site = iterator.next();
			System.out.print("P(Site"+siteNumber+"|" + site.get_parent().get_name() + "=Warm) = (");
			System.out.println(_iceInWarmProbability+", "+ (1-_iceInWarmProbability)+")  ; (Ice, Not_Ice)");
			System.out.print("P(Site"+siteNumber+"|" + site.get_parent().get_name() + "=Cold) = (");
			System.out.println(_iceInColdProbability+", "+ (1-_iceInColdProbability)+")  ; (Ice, Not_Ice)");
			System.out.print("P(Site"+siteNumber+"|" + site.get_parent().get_name() + "=Frigid) = (");
			System.out.println(_iceInFrigidProbability+", "+ (1-_iceInFrigidProbability)+")  ; (Ice, Not_Ice)");
			System.out.println();
			siteNumber++;
		}

		// Measurements:
		int measureNumber = 0;
		for (Iterator<Node> iterator = this._measurements.iterator(); iterator.hasNext();) {
			Node measure = iterator.next();
			System.out.print("P(Meas"+measureNumber+"|" + measure.get_parent().get_name() + "=Ice) = (");
			System.out.println(measure.getProbability(IceCondition.ICY, IceSense.SENSE_ICY)+", "+ measure.getProbability(IceCondition.ICY, IceSense.SENSE_NOT_ICY)+")  ; (Reading (1,0) resp.)");
			System.out.print("P(Meas"+measureNumber+"|" + measure.get_parent().get_name() + "=no Ice) = (");
			System.out.println(measure.getProbability(IceCondition.NOT_ICY, IceSense.SENSE_ICY)+", "+ measure.getProbability(IceCondition.NOT_ICY, IceSense.SENSE_NOT_ICY)+")  ; (Reading (1,0) resp.)");
			System.out.println();
			measureNumber++;
		}
	}

	public Vector<Node> get_all_sites() {
		return _sites;
	}
}
